package com.licode.codekit.buildprj;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.util.ResourceUtil;
import com.licode.codekit.db.EntityBuilder;
import com.licode.codekit.db.GenerateCtx;
import com.licode.codekit.db.Table;
import com.licode.codekit.utils.FileUtil;
import com.licode.codekit.utils.StrUtil;
import org.jsoup.internal.StringUtil;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * @author licode
 */
public class EntityCodeBuilder {
    final String MAPPER_HEAD = "org.mapstruct.Mapper;";
    final String MAPPER_ENTITY = MAPPER_HEAD + "\n\nimport {dto-package}.{entity-name}{entity-suffix};\n"
            + "import {po-package}.{entity-name}PO;";

    final String UTIL_LIST = "import java.util.List;\n" +
            "import org.mapstruct.Mapper;";

    final String ASSEMBLER_HEAD = "Assembler {";
    final String ASSEMBLER_FROM = "Assembler {\n\n    {entity-name}PO from{entity-name}{entity-suffix}({entity-name}{entity-suffix} source);\n"
            + "    {entity-name}{entity-suffix} from{entity-name}({entity-name}PO source);\n"
            + "    List<{entity-name}PO> from{entity-name}{entity-suffix}(List<{entity-name}{entity-suffix}> source);\n"
            + "    List<{entity-name}{entity-suffix}> from{entity-name}(List<{entity-name}PO> source);";

    final String ASSEMBLER_CONTENT = "package {service-package}.infrastructure.assembler;\n" +
            "\n" +
            "import java.util.List;\n" +
            "import org.mapstruct.Mapper;\n" +
            "\n" +
            "/**\n" +
            " * {description}\n" +
            " *\n" +
            " * @author {author}\n" +
            " * @date {date}\n" +
            " */\n" +
            "@Mapper(componentModel = \"spring\")\n" +
            "public interface {module-class-name}Assembler {\n" +
            "}\n";

    private String getRootDir(String dir) {
        String zipRoot = dir;
        if (dir != null && dir.length() >= 2) {
            if (!dir.endsWith("/")) {
                zipRoot = dir + "/";
            }

            return zipRoot;
        } else {
            return "";
        }
    }

    private int findClientAndServiceSrcDir(ProjectContext prjCtx, File file, List<String> subPath) {
        int findType = 0;
        File fList[] = file.listFiles();
        for (File f : fList) {
            String name = f.getName();
            if (f.isDirectory()) {
                findType = findClientAndServiceSrcDir(prjCtx, f, subPath);
            } else if (f.isFile()) {
                if (name.equals("ClientTag.java")) {
                    findType = 1;
                    prjCtx.setClientSrcDir(file.getAbsolutePath() + "/");
                    break;
                } else if (name.equals("ServiceTag.java")) {
                    findType = 2;
                    prjCtx.setServiceSrcDir(file.getAbsolutePath() + "/");
                    break;
                }
            }

            if (findType > 0) {
                if (f.isDirectory()) {
                    subPath.add(f.getName());
                }
                break;
            }
        }

        return findType;
    }

    private String createPackagePath(List<String> subPaths) {
        int i = 0;
        String dir = "";
        for (i = subPaths.size() - 1; i >= 0; --i) {
            dir += subPaths.get(i);
            if (i > 0) {
                dir += ".";
            }
        }
        return dir;
    }

    private String calcSrcJavaDir(String pkgPath) {
        String strPath = pkgPath.replace("/", "\\");
        int pos = pkgPath.indexOf(CodeKitConf.SRC_ROOT_PREFIX);
        if (pos == -1) {
            return strPath;
        } else {
            return strPath.substring(0, pos + CodeKitConf.SRC_ROOT_PREFIX.length());
        }
    }

    private String calcPackage(String pkgPath) {
        String strPath = pkgPath.replace("/", "\\");
        int pos = pkgPath.indexOf(CodeKitConf.SRC_ROOT_PREFIX);
        if (pos == -1) {
            return "";
        } else {
            return strPath.substring(pos + CodeKitConf.SRC_ROOT_PREFIX.length())
                    .replace("\\", ".");
        }
    }

    private void handleClientAndServiceSrcDir(ProjectContext prjCtx) {
        String rootDir = this.getRootDir(prjCtx.getRootDir()) + "/";
        File file = new File(rootDir);
        File fList[] = file.listFiles();
        List<File> subFiles = new ArrayList();

        prjCtx.setClientSrcDir("");
        prjCtx.setServiceSrcDir("");

        prjCtx.setServicePackage("");
        prjCtx.setClientPackage("");

        for (File f : fList) {
            if (f.isDirectory() &&
                    new File(f.getAbsolutePath() + "/pom.xml").exists()) {
                subFiles.add(f);
            }
        }

        /**
         * 如果没有按应用架构找到对应的client和service模块
         * 则直接搜索当前目录
         */
        if (subFiles.size() == 0) {
            subFiles.add(file);
        }

        for (File oneFile : subFiles) {
            int findType = 0;
            String javaDir = oneFile.getAbsolutePath() + "/src/main/java/";
            File javaFile = new File(javaDir);
            if (javaFile.exists()) {
                List<String> subPath = new ArrayList<>();
                findType = findClientAndServiceSrcDir(prjCtx, javaFile, subPath);
                if (findType > 0) {
                    if (findType == 1) {
                        prjCtx.setClientPackage(createPackagePath(subPath));
                        prjCtx.setHasClient(true);
                    } else if (findType == 2) {
                        prjCtx.setServicePackage(createPackagePath(subPath));
                    }
                }
            }
        }

        if (StringUtil.isBlank(prjCtx.getServicePackage())) {
            String servicePkg = prjCtx.getGroupId().replace(".", "/") + "/";
            String clientPkg = servicePkg + "client/";

            prjCtx.setClientSrcDir(rootDir + prjCtx.getModuleName() + "-"
                    + CodeKitConf.CLIENT_ROOT + clientPkg);
            prjCtx.setServiceSrcDir(rootDir + prjCtx.getModuleName() + "-"
                    + CodeKitConf.SERVICE_ROOT + servicePkg);

            prjCtx.setServicePackage(prjCtx.getGroupId());
            prjCtx.setClientPackage(prjCtx.getGroupId() + ".client");
        } else if (StringUtil.isBlank(prjCtx.getClientPackage())) {
            String moduleName = prjCtx.getModuleName();
            prjCtx.setClientSrcDir(prjCtx.getServiceSrcDir() + "client/");
            prjCtx.setClientPackage(prjCtx.getServicePackage() + ".client");
            if (moduleName.endsWith("-service")) {
                prjCtx.setModuleName(moduleName.substring(0, moduleName.length() - "-service".length()));
            } else if (moduleName.endsWith("-starter")) {
                prjCtx.setModuleName(moduleName.substring(0, moduleName.length() - "-starter".length()));
            }
        }
    }

    private boolean hasInvalidSubModule(ProjectContext prjCtx) {
        if (StringUtil.isBlank(prjCtx.getClientPackage()) ||
                StringUtil.isBlank(prjCtx.getServicePackage()) ||
                StringUtil.isBlank(prjCtx.getClientSrcDir()) ||
                StringUtil.isBlank(prjCtx.getServiceSrcDir())) {
            return true;
        } else if (!FileUtil.pathExist(prjCtx.getServiceSrcDir())) {
            return true;
        } else if (prjCtx.isHasClient() && !FileUtil.pathExist(prjCtx.getClientSrcDir())) {
            return true;
        } else {
            return false;
        }
    }

    public boolean buildEntityCode(Project rootPrj, ProjectContext prjCtx, String tempType) {
        boolean buildFlag = true;
        String rootDir = this.getRootDir(prjCtx.getRootDir()) + "/";
        File file = new File(rootDir);
        String tempFile = CodeKitConf.BM_DEFAULT.equals(tempType) ? "code.zip" : "do-code.zip";

        ClassLoader clsLoader = EntityCodeBuilder.class.getClassLoader();

        if (!(file.exists() && file.isDirectory())) {
            Messages.showMessageDialog(rootPrj, "项目不存在！",
                    "温馨提示", Messages.getInformationIcon());
            return false;
        }

        prjCtx.setHasClient(false);
        prjCtx.handleModulePackage();

        if (hasInvalidSubModule(prjCtx)) {
            handleClientAndServiceSrcDir(prjCtx);
        }

        if (hasInvalidSubModule(prjCtx)) {
            Messages.showMessageDialog(rootPrj, "请选择正确的项目！",
                    "温馨提示", Messages.getInformationIcon());
            return false;
        }

        prjCtx.setCurrentDir(rootDir);

        preHandleCodeTemplate(prjCtx, tempFile);

        for (Table table : prjCtx.getTables()) {
            try (InputStream is = FileUtil.getResourceAsStream(tempFile);
                 ZipInputStream isZip = new ZipInputStream(is, StandardCharsets.UTF_8)) {
                generateCodeByTables(prjCtx, isZip, table);
            } catch (Exception e) {
                buildFlag = false;
                break;
            }
        }

        if (!buildFlag) {
            Messages.showMessageDialog(rootPrj, "生成代码失败！",
                    "温馨提示", Messages.getInformationIcon());
        }

        return buildFlag;
    }

    private boolean preHandleCodeTemplate(ProjectContext prjCtx, String tempFile) {
        boolean result = false;

        try (InputStream is = FileUtil.getResourceAsStream(tempFile);
             ZipInputStream inputStream = new ZipInputStream(is, StandardCharsets.UTF_8)) {
            int pos;
            String name;
            String fileName;
            String midPackage;
            String pkgName;
            ZipEntry nextEntry = inputStream.getNextEntry();

            while (nextEntry != null) {
                name = nextEntry.getName();
                pos = name.lastIndexOf("/");

                fileName = name.substring(pos + 1);
                midPackage = name.substring(0, pos + 1);
                pkgName = prjCtx.getServicePackage();

                if (name.startsWith(CodeKitConf.SERVICE_PREFIX)) {
                    midPackage = midPackage.substring(CodeKitConf.SERVICE_PREFIX.length());
                } else if (name.startsWith(CodeKitConf.CLIENT_PREFIX)) {
                    midPackage = midPackage.substring(CodeKitConf.CLIENT_PREFIX.length());
                    pkgName = prjCtx.getClientPackage();
                }

                if ("TagPO.java".equals(fileName)) {
                    prjCtx.setPoPackage(pkgName + "." + midPackage.replace("/", "."));
                } else if ("TagDTO.java".equals(fileName) || "TagEntity.java".equals(fileName)) {
                    prjCtx.setEntitySuffix(StrUtil.getFileName(fileName.substring(3)));
                    prjCtx.setEntityPackage(pkgName + "." + midPackage.replace("/", "."));
                } else if ("TagAssembler.java".equals(fileName)) {
                    prjCtx.setAssemblerPackage(pkgName + "." + midPackage.replace("/", "."));
                }

                inputStream.closeEntry();
                nextEntry = inputStream.getNextEntry();
            }
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }

    private void generateCodeByTables(ProjectContext prjCtx,
                                      ZipInputStream inputStream,
                                      Table table) throws IOException {
        int pos;
        String name;
        String midDir;
        String fileName;
        String pathName = "";
        ZipEntry nextEntry = inputStream.getNextEntry();

        while (nextEntry != null) {
            name = nextEntry.getName();
            pathName = "";
            pos = name.lastIndexOf("/");

            fileName = name.substring(pos + 1);
            midDir = name.substring(0, pos + 1);

            if (name.startsWith(CodeKitConf.SERVICE_PREFIX)) {
                pathName = prjCtx.getServiceSrcDir() + midDir.substring(CodeKitConf.SERVICE_PREFIX.length());
            } else if (name.startsWith(CodeKitConf.CLIENT_PREFIX)) {
                pathName = prjCtx.getClientSrcDir() + midDir.substring(CodeKitConf.CLIENT_PREFIX.length());

                if (!prjCtx.isHasClient()) {
                    inputStream.closeEntry();
                    nextEntry = inputStream.getNextEntry();
                    continue;
                }
            }

            if (!StringUtil.isBlank(pathName)) {
                if (!nextEntry.isDirectory()) {
                    String result = FileUtil.readText(inputStream, "utf-8");
                    handleFileContent(prjCtx, table, pathName, fileName, result);
                } else {
                    FileUtil.makeDirs(pathName);
                }
            }

            inputStream.closeEntry();
            nextEntry = inputStream.getNextEntry();
        }
    }

    private String findAndCreateAssembler(String fileDir, String moduleClassName) {
        String assemblerFile;
        File file = new File(fileDir);
        if (!file.exists()) {
            file.mkdir();
        }
        File[] files = file.listFiles();
        if (null != files && files.length > 0) {
            assemblerFile = files[0].getAbsolutePath();
        } else {
            assemblerFile = fileDir + moduleClassName + "Assembler.java";
            try {
                File assFile = new File(assemblerFile);
                assFile.createNewFile();
                FileUtil.saveStrToFile(assFile, ASSEMBLER_CONTENT.getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return assemblerFile;
    }

    private void handleFileContent(ProjectContext prjCtx,
                                   Table table,
                                   String fileDir,
                                   String fileName,
                                   String result) {
        String filePath;
        GenerateCtx gCtx;
        EntityBuilder builder = new EntityBuilder();
        String moduleClassName = prjCtx.getModuleClassName();
        String entityName = prjCtx.getEntityName(table);

        if ("TagPO.java".equals(fileName) ||
                "TagDTO.java".equals(fileName) ||
                "TagEntity.java".equals(fileName)) {
            gCtx = new GenerateCtx(fileDir, StrUtil.getFileName(fileName.substring(3)));

            gCtx.setAuthor(prjCtx.getAuthor());
            gCtx.setTablePrefix(prjCtx.getTablePrefix());
            gCtx.setJdkVersion(prjCtx.getJdkVersion());
            gCtx.setSpringBoot2x(prjCtx.isSpringBoot2x());

            builder.generate(table, gCtx);
        } else if ("TagAssembler.java".equals(fileName)) {
            // 生成对象转换
            String tmPath = findAndCreateAssembler(fileDir, moduleClassName);
            String content = FileUtil.readFileContent(tmPath);
            String assemblerClassName = StrUtil.getFileName(tmPath);

            prjCtx.setAssemblerClassName(assemblerClassName);

            if (null != content && !content.isEmpty()) {
                if (!content.contains("from" + entityName + prjCtx.getEntitySuffix())) {
                    content = content.replace(MAPPER_HEAD, MAPPER_ENTITY
                            .replace("{group-id}", prjCtx.getGroupId())
                            .replace("{client-package}", prjCtx.getClientPackage())
                            .replace("{service-package}", prjCtx.getServicePackage())
                            .replace("{entity-suffix}", prjCtx.getEntitySuffix())
                            .replace("{name}", prjCtx.getModuleName())
                            .replace("{entity-name}", entityName));
                    content = prjCtx.handleContent(content, 0)
                            .replace(ASSEMBLER_HEAD, ASSEMBLER_FROM
                                    .replace("{entity-name}", entityName)
                                    .replace("{entity-suffix}", prjCtx.getEntitySuffix()));
                    saveStrToFile(tmPath, content.getBytes(StandardCharsets.UTF_8));
                }
            }
        } else {
            filePath = fileDir + handleFile(fileName, entityName);
            File saveFile = new File(filePath);
            if (!saveFile.exists()) {
                saveStrToFile(saveFile, this.handleEntityFile(prjCtx, result, table));
            }
        }
    }

    private String handleFile(String filename, String entityName) {
        if (filename.startsWith("EntityName")) {
            return entityName + filename.substring("EntityName".length());
        } else {
            return filename;
        }
    }

    private void saveStrToFile(String filepath, byte[] content) {
        File file = new File(filepath);
        saveStrToFile(file, content);
    }

    private void saveStrToFile(File filepath, byte[] content) {
        FileOutputStream fileOutputStream;
        BufferedOutputStream bufferedOutputStream;

        try {
            fileOutputStream = new FileOutputStream(filepath);
            bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
            bufferedOutputStream.write(content);
            bufferedOutputStream.close();
            fileOutputStream.close();
        } catch (Exception ignore) {
            ignore.printStackTrace();
        }
    }

    private byte[] handleEntityFile(ProjectContext prjCtx, String result, Table table) {
        String entityName = prjCtx.getEntityName(table);
        String entityField = StrUtil.firstLower(entityName);
        return prjCtx.handleContent(result)
                .replace("{entity-c}", StrUtil.getUrlPath(entityField))
                .replace("{entity-name}", entityName)
                .replace("{entity-field}", entityField)
                .getBytes(StandardCharsets.UTF_8);
    }
}
